package com.zx.log.aop;

import com.alibaba.fastjson2.JSON;
import com.zx.core.utils.IdUtils;
import com.zx.log.annotation.OperateLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * description: 操作日志切面，用于记录带有 @OperateLog 注解的方法调用信息。<br>
 * create: 2024-10-13 15:20
 * </p>
 *
 * @author zhou xun
 */
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE) // 设置最高优先级 优先级为1，数字越小，优先级越高
@Slf4j
public class OperateLogAspect {
    /**
     * 表示匹配带有 @OperateLog 自定义注解的方法。
     */
    @Pointcut("@annotation(com.zx.log.annotation.OperateLog)")
    public void pointcut() {
    }

    /**
     * 环绕通知，在方法执行前后记录操作日志。
     *
     * @param point 切点对象
     * @return 方法的返回值
     * @throws Throwable 如果方法执行过程中抛出异常
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start(point.getSignature().getName());
        String logId = IdUtils.getUUID();
        // 记录方法调用前的日志
        logBefore(point, logId);
        try {
            // 执行目标方法
            Object result = point.proceed();
            // 记录方法调用后的日志
            logAfter(point, stopWatch, result, logId);
            return result;
        } catch (Throwable e) {
            // 记录异常日志
            logException(point, stopWatch, e, logId);
            // 重新抛出异常，以便上层处理
            throw e;
        }
    }

    /**
     * 记录方法调用前的日志。
     *
     * @param point 切点对象
     * @param logId 日志ID
     */
    private void logBefore(ProceedingJoinPoint point, String logId) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        String className = point.getTarget().getClass().getName();
        String methodName = signature.getName();
        String args = Arrays.toString(point.getArgs());
        OperateLog operateLog = method.getAnnotation(OperateLog.class);
        if (operateLog != null) {
            log.info("日志ID: {},类名: {},调用开始: {},方法名: {}, 参数:{}",
                    logId, className, operateLog.value(), methodName, args);
        }
    }

    /**
     * 记录方法调用后的日志。
     *
     * @param point     切点对象
     * @param stopWatch 停止计时器
     * @param result    方法的返回值
     * @param logId     日志ID
     */
    private void logAfter(ProceedingJoinPoint point, StopWatch stopWatch, Object result, String logId) {
        stopWatch.stop();
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        String className = point.getTarget().getClass().getName();
        String methodName = signature.getName();
        OperateLog operateLog = method.getAnnotation(OperateLog.class);
        String executeTime = getExecuteTime(stopWatch);
        if (operateLog != null) {
            log.info("日志ID: {},类名: {},调用结束: {},方法名: {},执行时间: {},执行结果: {}",
                    logId, className, operateLog.value(), methodName, executeTime, JSON.toJSONString(result));
        }
    }

    /**
     * 记录方法调用过程中发生的异常日志。
     *
     * @param point     切点对象
     * @param stopWatch 停止计时器
     * @param e         异常对象
     * @param logId     日志ID
     */
    private void logException(ProceedingJoinPoint point, StopWatch stopWatch, Throwable e, String logId) {
        stopWatch.stop();
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        String className = point.getTarget().getClass().getName();
        String methodName = signature.getName();
        OperateLog operateLog = method.getAnnotation(OperateLog.class);
        String executeTime = getExecuteTime(stopWatch);
        if (operateLog != null) {
            log.error("日志ID: {}, 类名: {},调用异常: {},方法名: {},执行时间: {},异常信息: {}",
                    logId, className, operateLog.value(), methodName, executeTime, e.getMessage(), e);
        }
    }

    /**
     * 获取任务执行时间。
     *
     * @param stopWatch 停止计时器
     * @return 任务执行时间字符串
     */
    private String getExecuteTime(StopWatch stopWatch) {
        double totalTimeSeconds = stopWatch.getTotalTimeSeconds();
        if (totalTimeSeconds > 0) {
            stopWatch.prettyPrint();
            return String.format("%.2f %s", totalTimeSeconds, TimeUnit.SECONDS.toString().toLowerCase());
        } else {
            long totalTimeMillis = stopWatch.getTotalTimeMillis();
            if (totalTimeMillis > 0) {
                stopWatch.prettyPrint(TimeUnit.MILLISECONDS);
                return formatTime(totalTimeMillis, TimeUnit.MILLISECONDS);
            } else {
                stopWatch.prettyPrint(TimeUnit.NANOSECONDS);
                long totalTimeNanos = stopWatch.getTotalTimeNanos();
                return formatTime(totalTimeNanos, TimeUnit.NANOSECONDS);
            }
        }
    }

    /**
     * 格式化时间。
     *
     * @param time 时间值
     * @param unit 时间单位
     * @return 格式化后的时间字符串
     */
    private String formatTime(long time, TimeUnit unit) {
        return String.format("%d %s", time, unit.toString().toLowerCase());
    }
}